from web3 import Web3
import functools

from eth_typing.evm import (
    Address,)
from web3.types import TxReceipt
import os
import json
import math

from functools import wraps


MIN_TICK = -887272
MAX_TICK = -MIN_TICK
TICK_SPACING = {500: 10, 3_000: 60, 10_000: 200}


@functools.lru_cache()
def _load_abi(name: str) -> str:
  path = "data/coin1//abi/uniswap_v3"
  with open(os.path.join(path, f"{name}.abi")) as f:
    abi: str = json.load(f)
  return abi


@functools.lru_cache()
def _load_contract(w3,abi_name: str, address):
  address = Web3.to_checksum_address(address)
  return w3.eth.contract(address=address, abi=_load_abi(abi_name))


@functools.lru_cache()
def _addr_to_str(a) -> str:
  if isinstance(a, bytes):
    # Address or ChecksumAddress
    addr: str = Web3.to_checksum_address("0x" + bytes(a).hex())
    return addr
  elif isinstance(a, str) and a.startswith("0x"):
    addr = Web3.to_checksum_address(a)
    return addr

  raise RuntimeError("name not found")


def _get_min_tick(fee: int) -> int:
  min_tick_spacing: int = TICK_SPACING[fee]
  return -(MIN_TICK // -min_tick_spacing) * min_tick_spacing


def _get_max_tick(fee: int) -> int:
  max_tick_spacing: int = TICK_SPACING[fee]
  return (MAX_TICK // max_tick_spacing) * max_tick_spacing


def _default_tick_range(fee: int):
  min_tick = _get_min_tick(fee)
  max_tick = _get_max_tick(fee)

  return min_tick, max_tick


def _nearest_tick(tick: int, fee: int) -> int:
  min_tick, max_tick = _default_tick_range(fee)
  assert (min_tick <= tick <= max_tick), f"Provided tick is out of bounds: {(min_tick, max_tick)}"

  tick_spacing = TICK_SPACING[fee]
  rounded_tick_spacing = round(tick / tick_spacing) * tick_spacing

  if rounded_tick_spacing < min_tick:
    return rounded_tick_spacing + tick_spacing
  elif rounded_tick_spacing > max_tick:
    return rounded_tick_spacing - tick_spacing
  else:
    return rounded_tick_spacing


@functools.lru_cache()
def _parse_from_pool(pool):
  token_0 = pool.functions.token0().call()
  token_1 = pool.functions.token1().call()
  fee = pool.functions.fee().call()
  return {
      "token_base": token_0,
      "token_quote": token_1,
      "fee": fee,
  }


def _fixed_point_binary_to_float(input, integer_part_digits, floating_point_part_digits):
  sign_bit = 1 if (input >= 0) else -1
  input = abs(input) if sign_bit > 0 else (~(abs(input) - 1))
  return float(input) / (2**floating_point_part_digits) * sign_bit


def _sqrt_price_from_tick(tick):
  return 1.0001**(tick / 2)


def _tick_from_price(price):
  return int(math.log(price, 1.0001))


def _is_success_transaction(rx: TxReceipt):
  # assert isinstance(rx, TxReceipt)
  return rx["status"] == 1


def run_once(f):
  @wraps(f)
  def wrapper(*args, **kwargs):
      if not f.has_run:
          result = f(*args, **kwargs)
          f.has_run = True
          return result
  f.has_run = False
  return wrapper


def _calculate_amount(tick_lower: int, tick_upper: int, curr_tick: int, liquidity: int):
  if curr_tick < tick_lower:
      x, y = (
          liquidity
            * (_sqrt_price_from_tick(tick_upper) - _sqrt_price_from_tick(tick_lower))
          / _sqrt_price_from_tick(tick_upper + tick_lower)
      ), 0
  elif curr_tick > tick_upper:
      x, y = 0, (
          liquidity
            * (_sqrt_price_from_tick(tick_upper) - _sqrt_price_from_tick(tick_lower))
      )
  else:
      x, y = liquidity * (
          _sqrt_price_from_tick(tick_upper) - _sqrt_price_from_tick(curr_tick)
      ) / (
          _sqrt_price_from_tick(curr_tick) * _sqrt_price_from_tick(tick_upper)
      ), liquidity * (
          _sqrt_price_from_tick(curr_tick) - _sqrt_price_from_tick(tick_lower)
      )
  return x, y
